package cc.shacocloud.mirage.restful.http;

import cc.shacocloud.mirage.restful.exception.HttpMediaTypeNotAcceptableException;
import cc.shacocloud.mirage.restful.exception.InvalidMediaTypeException;
import cc.shacocloud.mirage.restful.util.HttpMethodUtils;
import cc.shacocloud.mirage.utils.charSequence.StrUtil;
import cc.shacocloud.mirage.utils.collection.CollUtil;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpMethod;
import org.jetbrains.annotations.Nullable;

import java.util.*;

import static io.vertx.core.http.HttpHeaders.*;

public class HttpHeaderMap implements MultiMap {
    
    public static final List<MediaType> MEDIA_TYPE_ALL_LIST = Collections.singletonList(MediaType.ALL);
    private final MultiMap headerMap;
    
    public HttpHeaderMap(MultiMap headerMap) {
        this.headerMap = headerMap;
    }
    
    /**
     * 返回媒体类型的身体，由指定 {@code Content-Type}首部
     */
    public MediaType getContentType() {
        String value = get(CONTENT_TYPE);
        return (StrUtil.isNotEmpty(value) ? MediaType.parseMediaType(value) : null);
    }
    
    /**
     * 设置主体的{@linkplain MediaType}，由{@code Content-Type}头指定。
     */
    public void setContentType(@Nullable MediaType mediaType) {
        if (mediaType != null) {
            if (mediaType.isWildcardType()) {
                throw new IllegalArgumentException("内容类型不能包含通配符类型 '*'");
            }
            if (mediaType.isWildcardSubtype()) {
                throw new IllegalArgumentException("内容类型不能包含通配符子类型 '*'");
            }
            set(CONTENT_TYPE, mediaType.toString());
        } else {
            remove(CONTENT_TYPE);
        }
    }
    
    /**
     * 返回主体的长度(以字节为单位)，由{@code Content-Length}头指定。
     * <p>
     * 当内容长度未知时返回-1。
     */
    public long getContentLength() {
        String value = get(CONTENT_LENGTH);
        return (value != null ? Long.parseLong(value) : -1);
    }
    
    
    /**
     * 设置主体的长度(以字节为单位)，由{@code Content-Length}头指定。
     */
    public void setContentLength(long contentLength) {
        set(CONTENT_LENGTH, Long.toString(contentLength));
    }
    
    /**
     * 获取可接受的媒体类型
     *
     * @throws HttpMediaTypeNotAcceptableException 如果无法解析请求的媒体类型则抛出该例外
     */
    public List<MediaType> getAcceptableMediaTypes() throws HttpMediaTypeNotAcceptableException {
        List<String> headerValues = getAll(ACCEPT);
        if (headerValues == null || headerValues.isEmpty()) {
            return MEDIA_TYPE_ALL_LIST;
        }
        
        try {
            List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
            MediaType.sortBySpecificityAndQuality(mediaTypes);
            return CollUtil.isNotEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;
        } catch (InvalidMediaTypeException ex) {
            throw new HttpMediaTypeNotAcceptableException("不能解析'Accept'头" + headerValues + ": " + ex.getMessage());
        }
        
    }
    
    /**
     * 返回{@code Origin}头的值。
     */
    @Nullable
    public String getOrigin() {
        return get(ORIGIN);
    }
    
    /**
     * 设置{@code Origin}头的(新)值。
     */
    public void setOrigin(@Nullable String origin) {
        setOrRemove(ORIGIN, origin);
    }
    
    /**
     * 返回{@code Access-Control-Request-Method}请求头的值。
     */
    @Nullable
    public HttpMethod getAccessControlRequestMethod() {
        return HttpMethodUtils.resolve(get(ACCESS_CONTROL_REQUEST_METHOD));
    }
    
    /**
     * 返回{@code Access-Control-Request-Headers}请求头的值。
     */
    public List<String> getAccessControlRequestHeaders() {
        return getAll(ACCESS_CONTROL_REQUEST_HEADERS);
    }
    
    /**
     * 设置响应头{@code Access-Control-Allow-Origin}的(新)值。
     */
    public void setAccessControlAllowOrigin(@Nullable String allowedOrigin) {
        setOrRemove(ACCESS_CONTROL_ALLOW_ORIGIN, allowedOrigin);
    }
    
    /**
     * 设置响应头{@code Access-Control-Allow-Methods}的(新)值。
     */
    public void setAccessControlAllowMethods(List<HttpMethod> allowedMethods) {
        set(ACCESS_CONTROL_ALLOW_METHODS, CollUtil.join(allowedMethods, ","));
    }
    
    /**
     * 设置响应头{@code Access-Control-Allow-Headers}的(新)值。
     */
    public void setAccessControlAllowHeaders(List<String> allowedHeaders) {
        set(ACCESS_CONTROL_ALLOW_HEADERS, String.join(",", allowedHeaders));
    }
    
    /**
     * 设置响应头{@code Access-Control-Expose-Headers}的(新)值。
     */
    public void setAccessControlExposeHeaders(List<String> exposedHeaders) {
        set(ACCESS_CONTROL_EXPOSE_HEADERS, String.join(",", exposedHeaders));
    }
    
    /**
     * 设置响应头{@code Access-Control-Allow-Credentials}的(新)值。
     */
    public void setAccessControlAllowCredentials(boolean allowCredentials) {
        set(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.toString(allowCredentials));
    }
    
    /**
     * 设置响应头{@code Access-Control-Max-Age}的(新)值。
     */
    public void setAccessControlMaxAge(long maxAge) {
        set(ACCESS_CONTROL_MAX_AGE, Long.toString(maxAge));
    }
    
    /**
     * 当前连接是否 keep-alive
     */
    public boolean isKeepAlive() {
        return KEEP_ALIVE.equals(get(CONNECTION));
    }
    
    // ---------------- 工具方法------------------------------------
    
    /**
     * 返回包含的键的集合视图
     */
    public Set<String> keySet() {
        Set<String> res = new HashSet<>();
        this.iterator().forEachRemaining(entry -> res.add(entry.getKey()));
        return Collections.unmodifiableSet(res);
    }
    
    /**
     * 设置给定的头值，或删除头如果{@code null}。
     *
     * @param headerName  标题名称
     * @param headerValue 头值，或{@code null}表示none
     */
    private void setOrRemove(CharSequence headerName, @Nullable CharSequence headerValue) {
        if (headerValue != null) {
            set(headerName, headerValue);
        } else {
            remove(headerName);
        }
    }
    
    
    // ------------------ MultiMap ---------------------------------------------------
    
    @Override
    public String get(CharSequence name) {
        return headerMap.get(name);
    }
    
    @Override
    public String get(String name) {
        return headerMap.get(name);
    }
    
    @Override
    public List<String> getAll(String name) {
        return headerMap.getAll(name);
    }
    
    @Override
    public List<String> getAll(CharSequence name) {
        return headerMap.getAll(name);
    }
    
    @Override
    public boolean contains(String name) {
        return headerMap.contains(name);
    }
    
    @Override
    public boolean contains(CharSequence name) {
        return headerMap.contains(name);
    }
    
    @Override
    public boolean isEmpty() {
        return headerMap.isEmpty();
    }
    
    @Override
    public Set<String> names() {
        return headerMap.names();
    }
    
    @Override
    public MultiMap add(String name, String value) {
        return headerMap.add(name, value);
    }
    
    @Override
    public MultiMap add(CharSequence name, CharSequence value) {
        return headerMap.add(name, value);
    }
    
    @Override
    public MultiMap add(String name, Iterable<String> values) {
        return headerMap.add(name, values);
    }
    
    @Override
    public MultiMap add(CharSequence name, Iterable<CharSequence> values) {
        return headerMap.add(name, values);
    }
    
    @Override
    public MultiMap addAll(MultiMap map) {
        return headerMap.addAll(map);
    }
    
    @Override
    public MultiMap addAll(Map<String, String> headers) {
        return headerMap.addAll(headers);
    }
    
    @Override
    public MultiMap set(String name, String value) {
        return headerMap.set(name, value);
    }
    
    @Override
    public MultiMap set(CharSequence name, CharSequence value) {
        return headerMap.set(name, value);
    }
    
    @Override
    public MultiMap set(String name, Iterable<String> values) {
        return headerMap.set(name, values);
    }
    
    @Override
    public MultiMap set(CharSequence name, Iterable<CharSequence> values) {
        return headerMap.set(name, values);
    }
    
    @Override
    public MultiMap setAll(MultiMap map) {
        return headerMap.setAll(map);
    }
    
    @Override
    public MultiMap setAll(Map<String, String> headers) {
        return headerMap.setAll(headers);
    }
    
    @Override
    public MultiMap remove(String name) {
        return headerMap.remove(name);
    }
    
    @Override
    public MultiMap remove(CharSequence name) {
        return headerMap.remove(name);
    }
    
    @Override
    public MultiMap clear() {
        return headerMap.clear();
    }
    
    @Override
    public int size() {
        return headerMap.size();
    }
    
    @Override
    public Iterator<Map.Entry<String, String>> iterator() {
        return headerMap.iterator();
    }
    
    @Override
    public String toString() {
        return "{\n" +
                headerMap +
                "}";
    }
    
    
}
